package org.gdstash.db;

import org.gdstash.file.ARZRecord;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class DBShrine {
    public static final String TABLE_NAME = "GD_SHRINE";
    public static final String FIELD_ID = "SHRINE_ID";
    private static final int ROW_SHRINE_ID = 1;
    private static final int ROW_NAME = 2;
    private static final int ROW_NORMAL_DISABLED = 3;
    private static final int ROW_NORMAL_LOCKED = 4;
    private static final int ROW_EPIC_DISABLED = 5;
    private static final int ROW_EPIC_LOCKED = 6;
    private static final int ROW_LEGENDARY_DISABLED = 7;
    private static final int ROW_LEGENDARY_LOCKED = 8;
    private static ConcurrentHashMap<String, DBShrine> hashBuffer;
    private String shrineID;
    private String name;
    private boolean normalDisabled;
    private boolean normalLocked;
    private boolean epicDisabled;
    private boolean epicLocked;
    private boolean legendaryDisabled;
    private boolean legendaryLocked;

    public DBShrine() {
        this.shrineID = null;
        this.name = null;
        this.normalDisabled = false;
        this.normalLocked = false;
        this.epicDisabled = false;
        this.epicLocked = false;
        this.legendaryDisabled = false;
        this.legendaryLocked = false;
    }

    private DBShrine(final ARZRecord record) {
        this.shrineID = record.getFileName();
        this.name = record.getShrineName();
        this.normalDisabled = record.getShrineNormalDisabled();
        this.normalLocked = record.getShrineNormalLocked();
        this.epicDisabled = record.getShrineEpicDisabled();
        this.epicLocked = record.getShrineEpicLocked();
        this.legendaryDisabled = record.getShrineLegendaryDisabled();
        this.legendaryLocked = record.getShrineLegendaryLocked();
    }

    static {
        DBShrine.hashBuffer = new ConcurrentHashMap<String, DBShrine>();
    }

    private static List<DBShrine> wrap(final ResultSet rs) throws SQLException {
        final LinkedList<DBShrine> list = new LinkedList<DBShrine>();
        while (rs.next()) {
            final DBShrine shrine = new DBShrine();
            shrine.shrineID = rs.getString(1);
            shrine.name = rs.getString(2);
            shrine.normalDisabled = rs.getBoolean(3);
            shrine.normalLocked = rs.getBoolean(4);
            shrine.epicDisabled = rs.getBoolean(5);
            shrine.epicLocked = rs.getBoolean(6);
            shrine.legendaryDisabled = rs.getBoolean(7);
            shrine.legendaryLocked = rs.getBoolean(8);
            if (shrine.name != null) {
                final ByteBuffer buffer = StandardCharsets.UTF_8.encode(shrine.name);
                final String utf8 = StandardCharsets.UTF_8.decode(buffer).toString();
                shrine.name = utf8;
            }
            list.add(shrine);
        }
        return list;
    }

    public String getShrineID() {
        return this.shrineID;
    }

    public static boolean doesExist(final String shrineID) {
        String id = shrineID;
        if (!id.startsWith("records/interactive/")) {
            id = "records/interactive/" + id;
        }
        final DBShrine shrine = get(id);
        return shrine != null;
    }

    public static boolean isActive(final String shrineID, final int difficulty) {
        String id = shrineID;
        if (!id.startsWith("records/interactive/")) {
            id = "records/interactive/" + id;
        }
        final DBShrine shrine = get(id);
        return shrine != null && shrine.isActive(difficulty);
    }

    public static Info getShrineInfo(final String shrineID, final int difficulty) {
        final Info info = new Info();
        try {
            if (!GDDBUtil.tableExists("GD_SHRINE")) {
                return info;
            }
        } catch (SQLException ex) {
            return info;
        }
        String id = shrineID;
        if (!id.startsWith("records/")) {
            id = "records/interactive/" + id;
        }
        final DBShrine shrine = get(id);
        info.exists = (shrine != null);
        if (info.exists) {
            info.active = shrine.isActive(difficulty);
        }
        return info;
    }

    public static void clearBuffer() {
        DBShrine.hashBuffer.clear();
    }

    public static void createTables() throws SQLException {
        final String dropTable = "DROP TABLE GD_SHRINE";
        final String createTable = "CREATE TABLE GD_SHRINE (SHRINE_ID VARCHAR(256) NOT NULL, NAME               VARCHAR(256), NORMAL_DISBALED    BOOLEAN, NORMAL_LOCKED      BOOLEAN, EPIC_DISBALED      BOOLEAN, EPIC_LOCKED        BOOLEAN, LEGENDARY_DISBALED BOOLEAN, LEGEBNDARY_LOCKED  BOOLEAN, PRIMARY KEY (SHRINE_ID))";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final Statement st = conn.createStatement()) {
                if (GDDBUtil.tableExists(conn, "GD_SHRINE")) {
                    st.execute(dropTable);
                }
                st.execute(createTable);
                st.close();
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {"GD_SHRINE"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_CREATE_TABLE", args);
                GDMsgLogger.addError(msg);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static void delete(final String shrineID) throws SQLException {
        final String deleteEntry = "DELETE FROM GD_SHRINE WHERE SHRINE_ID = ?";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(deleteEntry)) {
                ps.setString(1, shrineID);
                ps.executeUpdate();
                ps.close();
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {shrineID, "GD_SHRINE"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_DEL_TABLE_BY_ID", args);
                GDMsgLogger.addError(msg);
                GDMsgLogger.addError(ex);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        } catch (SQLException ex2) {
            throw ex2;
        }
    }

    public static void insert(final ARZRecord record) throws SQLException {
        final DBShrine entry = get(record.getFileName());
        if (entry != null) {
            return;
        }
        final DBShrine shrine = new DBShrine(record);
        final String insert = "INSERT INTO GD_SHRINE VALUES (?,?,?,?,?,?,?,?)";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(insert)) {
                ps.setString(1, shrine.shrineID);
                ps.setString(2, shrine.name);
                ps.setBoolean(3, shrine.normalDisabled);
                ps.setBoolean(4, shrine.normalLocked);
                ps.setBoolean(5, shrine.epicDisabled);
                ps.setBoolean(6, shrine.epicLocked);
                ps.setBoolean(7, shrine.legendaryDisabled);
                ps.setBoolean(8, shrine.legendaryLocked);
                ps.executeUpdate();
                ps.close();
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {record.getFileName(), "GD_SHRINE"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_INS_TABLE_BY_ID", args);
                GDMsgLogger.addError(msg);
                GDMsgLogger.addError(ex);
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static DBShrine get(final String shrineID) {
        DBShrine shrine = null;
        shrine = DBShrine.hashBuffer.get(shrineID);
        if (shrine == null) {
            shrine = getDB(shrineID);
            if (shrine != null) {
                DBShrine.hashBuffer.put(shrine.shrineID, shrine);
            }
        }
        return shrine;
    }

    private static DBShrine getDB(final String shrineID) {
        DBShrine shrine = null;
        final String command = "SELECT * FROM GD_SHRINE WHERE SHRINE_ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, shrineID);
            try (final ResultSet rs = ps.executeQuery()) {
                final List<DBShrine> list = wrap(rs);
                if (list.isEmpty()) {
                    shrine = null;
                } else {
                    shrine = list.get(0);
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            final Object[] args = {shrineID, "GD_SHRINE"};
            final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_READ_TABLE_BY_ID", args);
            GDMsgLogger.addError(msg);
            GDMsgLogger.addError(ex2);
        }
        return shrine;
    }

    public String getName() {
        return this.name;
    }

    public boolean isActive(final int difficulty) {
        if (difficulty == 0) {
            return !this.normalDisabled;
        }
        if (difficulty == 1) {
            return !this.epicDisabled;
        }
        return difficulty == 2 && !this.legendaryDisabled;
    }

    public static class Info {
        public boolean exists;
        public boolean active;

        public Info() {
            this.exists = false;
            this.active = false;
        }
    }
}
